﻿#include "pch.h"
#include "ThreadpoolTimer.h"
#include "AsyncLibImpl.h"

namespace AsyncLib
{

CThreadpoolTimer::CThreadpoolTimer() : m_threadId(0)
{
}

CThreadpoolTimer::~CThreadpoolTimer()
{
	assert(m_queTasks.empty());
	assert(m_threadId == 0);
}

void CThreadpoolTimer::Init()
{
	[[maybe_unused]] int err = pthread_create(&m_threadId, nullptr, [](void *pThis)
	{
		((CThreadpoolTimer *)pThis)->ThreadProc();
		return (void *)0;
	}, this);

	assert(err == 0);
}

void CThreadpoolTimer::Stop()
{
	// Lock to prevent wait_until after notify_one
	std::unique_lock lock(m_mutex);
	m_condVar.notify_one();
}

void CThreadpoolTimer::Exit()
{
	// Wait for the thread to exit
	if (m_threadId)
	{
		pthread_join(m_threadId, nullptr);
		m_threadId = 0;
	}

	// Destruct pending tasks sequentially
	while (m_queTasks.size())
	{
		m_queTasks.pop();
	}
}

void CThreadpoolTimer::AddTask(UINT nMilliseconds, std::function<void()>&& fnTask)
{
	auto dueTime = std::chrono::system_clock::now() + std::chrono::milliseconds(nMilliseconds);
	TimeTask task = { dueTime, std::move(fnTask) };

	std::unique_lock lock(m_mutex);
	m_queTasks.emplace(std::move(task));

	// If the new task expires first, reset the timer
	if (m_queTasks.top().dueTime == dueTime)
		m_condVar.notify_one();
}

void CThreadpoolTimer::ThreadProc()
{
	std::unique_lock lock(m_mutex);

	while (g_asyncLib.IsRunning())
	{
		if (m_queTasks.empty())
		{
			m_condVar.wait(lock);
		}
		else if (m_queTasks.top().dueTime > std::chrono::system_clock::now())
		{
			m_condVar.wait_until(lock, m_queTasks.top().dueTime);
		}
		else
		{
			auto fnTask = std::move(((TimeTask&)m_queTasks.top()).fnTask);
			m_queTasks.pop();
			lock.unlock();

			g_asyncLib.PostTask(ATID_TP, std::move(fnTask));
			fnTask = nullptr;

			lock.lock();
		}
	}
}

} // namespace AsyncLib
